package pl.com.qapps.unity;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import pl.com.qapps.unity.UnityPb.RequestPb;
import pl.com.qapps.unity.UnityPb.ResponsePb;

/**
 * @author Jakub Dykowski
 * 
 */
final class Pipe implements Closeable {

	private static final int MAX = 255;
	private static final int MIN = 0;

	private final InputStream in;
	private final OutputStream out;
	// private BlockingQueue<ResponsePb> responses = new
	// LinkedBlockingQueue<>();
	// private final ExecutorService executor = Executors
	// .newSingleThreadExecutor();
	private boolean closed = false;

	Pipe(OutputStream out, InputStream in) {
		assert out != null : "output cannot be null";
		assert in != null : "input cannot be null";
		this.in = in;
		this.out = out;
	}

	void send(RequestPb message) throws IOException {
		assert message != null : "cannot send null message";
		writeSize(message.getSerializedSize());
		message.writeTo(out);
		out.flush();
		// try {
		// System.out.println("waiting for response...");
		// return responses.take();
		// } catch (InterruptedException e) {
		// throw new IOException(e);
		// }
	}

	ResponsePb receive(ResponsePb.Builder builder) throws IOException {
		return builder.mergeFrom(
				new DelimitedInputStream(in, readSize())).build();
		// TODO get rid of this in future
		// if (res.getResult() == Status.FAILURE)
		// throw new RuntimeException("Implementation failure: "
		// + res.getErrorMessage());
	}

	private void writeSize(int size) throws IOException {
		assert size > 0 : "size cannot be < than 0";
		if (size >= MAX) {
			for (int i = 0; i < size / MAX; i++) {
				out.write(MAX);
			}
			out.write(size % MAX);
		} else
			out.write(size);
	}

	private int readSize() throws IOException {
		int size = 0, b;
		do {
			b = in.read();
			if (b < MIN)
				throw new IOException("Unexpected EOF reached, current size: "
						+ size + ", current byte: " + b);
			size += b;
		} while (b == MAX);
		return size;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "<" + getClass().getSimpleName() + ">";
	}

	/*
	 * // * (non-Javadoc) // * // * @see java.io.Closeable#close() //
	 */
	@Override
	public final void close() throws IOException {
		if (!closed) {
			System.out.println("closing " + this);
			closed = true;
			// executor.shutdownNow();
			in.close();
			out.close();
		}
	}

	private class DelimitedInputStream extends InputStream {

		private final InputStream wrapped;
		private final int size;
		private int read = 0;

		private DelimitedInputStream(InputStream wrapped, int size) {
			this.size = size;
			this.wrapped = wrapped;
		}

		@Override
		public int read() throws IOException {
			if (++read > size)
				return -1;
			else
				return wrapped.read();
		}

	}
}
